use super::*;
use anyhow::{anyhow, Result};
use std::fs::File;
use std::path::Path;

impl Book {
    pub fn save_as_text(&self, file: impl AsRef<Path>) -> Result<()> {
        use std::io::{BufWriter, Write};
        let mut writer = BufWriter::new(File::create(file)?);
        writeln!(&mut writer, "name: {}", self.name)?;
        writeln!(&mut writer, "desc:")?;
        for line in self.description.lines() {
            writeln!(&mut writer, "\t{}", line)?;
        }
        writeln!(&mut writer, "start: {}", self.start)?;
        writeln!(&mut writer, "turn: {}", self.turn.to_char() as char)?;
        writeln!(&mut writer, "named_situations:")?;
        for (situation_id, turn, name) in &self.named_situations {
            writeln!(
                &mut writer,
                "\t{},{},{}",
                situation_id,
                turn.to_char() as char,
                name,
            )?;
        }
        writeln!(&mut writer, "situations:")?;
        for situation in &self.situations {
            writeln!(
                &mut writer,
                "\t{},{},{}",
                situation.id,
                std::str::from_utf8(&situation.pieces).unwrap(),
                join_items(&situation.steps, ","),
            )?;
        }
        Ok(())
    }

    pub fn load_from_text(file: impl AsRef<Path>) -> Result<Book> {
        use std::io::{BufRead, BufReader, Seek, SeekFrom};
        let mut reader = BufReader::new(File::open(file)?);
        let mut line_number = 0;
        fn read_field<R: BufRead>(
            reader: &mut R,
            field: &str,
            line_number: &mut usize,
        ) -> Result<String> {
            let mut line = String::new();
            let num_bytes = reader.read_line(&mut line)?;
            if num_bytes > 0 {
                *line_number += 1;
                let mut parts = line.splitn(2, ':');
                if parts.next() == Some(field) {
                    if let Some(value) = parts.next() {
                        return Ok(value.trim().to_string());
                    }
                }
            }
            Err(anyhow!("line {line_number}: Expect {field} field"))
        }
        fn read_lines<R: BufRead + Seek>(
            reader: &mut R,
            indent: &str,
            line_number: &mut usize,
        ) -> Result<Vec<(usize, String)>> {
            let mut lines = Vec::new();
            loop {
                let position = reader.stream_position()?;
                let mut line = String::new();
                let num_bytes = reader.read_line(&mut line)?;
                if num_bytes > 0 {
                    *line_number += 1;
                    if let Some(content) = line.strip_prefix(indent) {
                        let content = content.strip_suffix('\n').unwrap_or(content);
                        lines.push((*line_number, content.to_string()));
                    } else {
                        *line_number -= 1;
                        reader.seek(SeekFrom::Start(position))?;
                        break;
                    }
                } else {
                    break;
                }
            }
            Ok(lines)
        }
        let name = read_field(&mut reader, "name", &mut line_number)?;

        let mut description = read_field(&mut reader, "desc", &mut line_number)?;
        for (_, line) in read_lines(&mut reader, "\t", &mut line_number)? {
            description.push_str("\n");
            description.push_str(&line);
        }

        let start = read_field(&mut reader, "start", &mut line_number)?
            .parse::<SituationId>()
            .map_err(|e| anyhow!(e))?;
        let turn = read_field(&mut reader, "turn", &mut line_number)?
            .as_str()
            .try_into()
            .map_err(|e: &str| anyhow!(e))?;

        read_field(&mut reader, "named_situations", &mut line_number)?;
        let mut named_situations = Vec::new();
        for (number, line) in read_lines(&mut reader, "\t", &mut line_number)? {
            let parts = line.splitn(3, ',').collect::<Vec<_>>();
            if parts.len() == 3 {
                let id = parts[0]
                    .parse::<SituationId>()
                    .map_err(|e| anyhow!(format!("line {number}: {e} of id")))?;
                let turn = parts[1].try_into().map_err(|e: &str| anyhow!(e))?;
                let name = parts[2].to_string();
                named_situations.push((id, turn, name));
            }
        }
        read_field(&mut reader, "situations", &mut line_number)?;
        let mut situations = Vec::new();
        for (number, line) in read_lines(&mut reader, "\t", &mut line_number)? {
            let parts = line.splitn(3, ',').collect::<Vec<_>>();
            if parts.len() == 3 {
                let id = parts[0]
                    .parse::<SituationId>()
                    .map_err(|e| anyhow!(format!("line {number}: {e} of id")))?;
                let pieces = slice_to_array::<90, u8>(parts[1].as_bytes(), b'_');
                let steps = if parts[2] == "" {
                    vec![]
                } else {
                    parts[2]
                        .split(',')
                        .map(|step| {
                            let items = step.split('-').collect::<Vec<_>>();
                            if items.len() >= 3 {
                                let from = Pos(items[0]
                                    .parse::<u8>()
                                    .map_err(|e| anyhow!(format!("line {number}: {e} of from")))?);
                                let to = Pos(items[1]
                                    .parse::<u8>()
                                    .map_err(|e| anyhow!(format!("line {number}: {e} of to")))?);
                                let next = items[2]
                                    .parse::<SituationId>()
                                    .map_err(|e| anyhow!(format!("line {number}: {e} of id")))?;
                                let mut step = Step::new(from, to, next);
                                if items.len() >= 4 {
                                    let score = items[3].parse::<u8>().map_err(|e| {
                                        anyhow!(format!("line {number}: {e} of score"))
                                    })?;
                                    step.score = score;
                                }
                                Ok(step)
                            } else {
                                Err(anyhow!(format!("line {number}: invalid step")))
                            }
                        })
                        .collect::<Result<Vec<Step>, anyhow::Error>>()?
                };
                situations.push(Situation { id, pieces, steps });
            } else {
                return Err(anyhow!("line {number}: Invalid situation"));
            }
        }
        Ok(Book {
            name,
            description,
            start,
            turn,
            named_situations,
            situations,
        })
    }
}

pub fn join_items<T: ToString>(items: &[T], separator: &str) -> String {
    items
        .iter()
        .map(|it| it.to_string())
        .collect::<Vec<_>>()
        .join(separator)
}

fn slice_to_array<const N: usize, T: Copy>(data: &[T], initial: T) -> [T; N] {
    let mut arr = [initial; N];
    for i in 0..N {
        arr[i] = data[i];
    }
    arr
}
